#pragma once

#include "allocator.hpp"
#include "utility.hpp"
#include "reverse_iterator.hpp"

namespace zuck
{
	template<typename _Array>
	class _ValArrayConstIterator {
	private:
		using _Self = _ValArrayConstIterator<_Array>;
	public:
		using value_type		= typename _Array::value_type;
		using pointer			= value_type const*;
		using reference			= value_type const&;
		using difference_type	= typename _Array::difference_type;
		using iterator_category = zuck::random_access_iterator_tag;
	private:
		pointer m_iter;
		pointer m_start;
		pointer m_last;
	public:
		_ValArrayConstIterator(pointer iter, pointer start, pointer last) 
			: m_start(start), m_last(last), m_iter(iter)
		{}

		_ValArrayConstIterator(const _Self& other)
			: m_iter(other.m_iter), m_start(other.m_start)
			, m_last(other.m_last)
		{}

		_ValArrayConstIterator(_Self&& other) 
			: m_iter(other.m_iter), m_start(other.m_start)
			, m_last(other.m_last) 
		{
			other.m_iter  = null;
			other.m_start = null;
			other.m_last  = null;
		}

		virtual ~_ValArrayConstIterator()
		{
			m_iter  = null;
			m_start = null;
			m_last  = null;
		}

		ZNODISCARD inline pointer get_unwrapped()const noexcept
		{ return m_iter; }

	public:
		inline _Self& operator=(const _Self& other)
		{
			m_iter  = other.m_iter;
			m_start = other.m_start;
			m_last  = other.m_last;
			return *this;
		}

		inline _Self& operator=(_Self&& other)noexcept
		{
			m_iter  = other.m_iter;
			m_start = other.m_start;
			m_last  = other.m_last;
			other.m_iter  = null;
			other.m_start = null;
			other.m_last  = null;
			return *this;
		}

		[[nodiscard]] inline pointer operator->()const noexcept
		{
			if constexpr (debug_) {
				if (m_iter < m_start || m_iter >= m_last) {
					derror(zuck::SEG_WrongIter,
						"Segmentfault: invalid iter address !");
				}
			}
			return m_iter;
		}

		[[nodiscard]] inline reference operator*()const noexcept
		{
			if constexpr (debug_) {
				if (m_iter < m_start || m_iter >= m_last) {
					derror(zuck::SEG_WrongIter,
						"Segmentfault: invalid iter address !");
				}
			}
			return *m_iter;
		}

		inline _Self& operator++()noexcept
		{
			if constexpr (debug_) {
				if (m_iter < m_start || m_iter >= m_last) {
					derror(zuck::SEG_WrongIter,
						"Segmentfault: invalid iter address !");
				}
			}
			++m_iter;
			return *this;
		}

		[[nodiscard]] inline _Self operator++(int)noexcept
		{
			auto _Tmp = *this;
			++(*this);
			return _Tmp;
		}

		inline _Self& operator+=(difference_type const _Step)noexcept
		{
			if constexpr (debug_) {
				auto const _Size = m_last - m_iter;
				if (_Size < _Step) {
					derror(zuck::SEG_OutofRange,
						"Segmentfault: step is too long !");
				}
			}
			m_iter += _Step;
			return *this;
		}

		[[nodiscard]] inline _Self operator+(const difference_type _Step)const noexcept
		{
			auto _Tmp = *this;
			return _Tmp += _Step;
		}

		[[nodiscard]] inline reference operator[](const difference_type _Idx)const noexcept
		{
			return *((*this) + _Idx);
		}

		inline _Self& operator--()noexcept
		{
			if constexpr (debug_) {
				if (m_iter < m_start || m_iter >= m_last) {
					derror(zuck::SEG_WrongIter,
						"Segmentfault: invalid iter address !");
				}
			}
			--m_iter;
			return *this;
		}

		[[nodiscard]] inline _Self operator--(int)noexcept
		{
			auto _Tmp = *this;
			--(*this);
			return _Tmp;
		}

		inline _Self& operator-=(const difference_type _Step)noexcept
		{
			if constexpr (debug_) {
				auto const _Size = m_last - m_start;
				if (_Size < _Step) {
					derror(zuck::SEG_OutofRange,
						"Segmentfault: step is too long !");
				}
			}
			m_iter -= _Step;
			return *this;
		}

		[[nodiscard]] inline _Self operator-(const difference_type _Step)const noexcept
		{
			auto _Tmp = *this;
			return _Tmp -= _Step;
		}

		[[nodiscard]] inline difference_type operator-(const _Self& other)const noexcept
		{
			return static_cast<difference_type>(m_iter - other.m_iter);
		}

		[[nodiscard]] inline difference_type operator-(pointer iter)const noexcept
		{
			return static_cast<difference_type>(m_iter - iter);
		}

	public:
		[[nodiscard]] inline bool operator==(const _Self& other)const noexcept
		{
			return static_cast<bool>(m_iter == other.m_iter);
		}

		[[nodiscard]] inline bool operator!=(const _Self& other)const noexcept
		{
			return static_cast<bool>(m_iter != other.m_iter);
		}

		[[nodiscard]] inline bool operator<(const _Self& other)const noexcept
		{
			return static_cast<bool>(m_iter < other.m_iter);
		}

		[[nodiscard]] inline bool operator<=(const _Self& other)const noexcept
		{
			return static_cast<bool>(m_iter <= other.m_iter);
		}

		[[nodiscard]] inline bool operator>(const _Self& other)const noexcept
		{
			return static_cast<bool>(m_iter > other.m_iter);
		}

		[[nodiscard]] inline bool operator>=(const _Self& other)const noexcept
		{
			return static_cast<bool>(m_iter >= other.m_iter);
		}

	};

	template<typename _Array>
	class _ValArrayIterator
		: public _ValArrayConstIterator<_Array>
	{
	private:
		using _Self = _ValArrayIterator<_Array>;
		using _Base = _ValArrayConstIterator<_Array>;
	public:
		using value_type		= typename _Base::value_type;
		using pointer			= value_type*;
		using reference			= value_type&;
		using difference_type	= typename _Base::difference_type;
		using iterator_category = typename _Base::iterator_category;
	public:
		explicit _ValArrayIterator(pointer iter, pointer start, pointer last)
			: _Base(iter, start, last) {}

		_ValArrayIterator(_Self const& other)
			: _Base(other) {}

		_ValArrayIterator(_Self&& other)
			: _Base(zuck::move(other)) {}

	public:
		inline _Self& operator=(const _Self& other)
		{
			_Base::operator=(other);
			return *this;
		}

		inline _Self& operator=(_Self&& other)noexcept
		{
			_Base::operator=(zuck::move(other));
			return *this;
		}

		[[nodiscard]] inline pointer operator->()noexcept
		{
			return const_cast<pointer>(_Base::operator->());
		}

		[[nodiscard]] inline reference operator*()noexcept
		{
			return const_cast<reference>(_Base::operator*());
		}

		inline _Self& operator++()noexcept
		{
			_Base::operator++();
			return *this;
		}

		inline _Self operator++(int)noexcept
		{
			auto _Tmp = *this;
			++(*this);
			return _Tmp;
		}

		inline _Self& operator+=(const difference_type _Step)noexcept
		{
			_Base::operator+=(_Step);
			return *this;
		}

		[[nodiscard]] inline _Self operator+(const difference_type _Step)const noexcept
		{
			auto _Tmp = *this;
			return _Tmp += _Step;
		}

		[[nodiscard]] const  reference operator[](difference_type const _Idx)const noexcept
		{
			return const_cast<reference>(_Base::operator[](_Idx));
		}

		inline _Self& operator--()noexcept
		{
			_Base::operator--();
			return *this;
		}

		inline _Self operator--(int)noexcept {
			auto _Tmp = *this;
			--(*this);
			return _Tmp;
		}

		inline _Self& operator-=(difference_type const _Step)noexcept
		{
			_Base::operator-=(_Step);
			return *this;
		}

		[[nodiscard]] inline _Self operator-(difference_type const _Step)const noexcept
		{
			auto _Tmp = *this;
			return _Tmp -= _Step;
		}

		[[nodiscard]] inline difference_type operator-(_Self const& other)const noexcept
		{
			return _Base::operator-(other);
		}

		[[nodiscard]] inline difference_type operator-(pointer iter)const noexcept
		{
			return _Base::operator-(iter);
		}

	};


	template<typename T, typename Alloc = zuck::allocator<T>>
	class valArray {
		static_assert(!zuck::is_base_type_v<T>, 
			"template parameter \"T\" must be primitive data types !");
	private:
        using _Self        = valArray<T, Alloc>;
		using _Alty_tts = zuck::allocator_traits<Alloc>;
	public:
		using value_type	  = T;
		using pointer		  = typename _Alty_tts::pointer;
		using const_pointer	  = typename _Alty_tts::const_pointer;
		using size_type		  = typename _Alty_tts::size_type;
		using reference       = typename _Alty_tts::reference;
		using const_reference = typename _Alty_tts::const_reference;
		using difference_type = typename _Alty_tts::difference_type;
		using allocator_type  = typename _Alty_tts::template rebind_alloc<T>;
	public:
		using iterator				 = _ValArrayIterator<valArray<T, Alloc>>;
		using const_iterator		 = _ValArrayConstIterator<valArray<T, Alloc>>;
		using reverse_iterator		 = zuck::reverse_iterator<_ValArrayIterator<valArray<T, Alloc>>>;
		using const_reverse_iterator = zuck::reverse_iterator<_ValArrayConstIterator<valArray<T, Alloc>>>;
	private:
		pointer _m_start;
		pointer _m_last;
		Alloc   alloc;
	public:
		constexpr valArray() 
			: _m_start(), _m_last(), alloc(Alloc{}) {}

		explicit valArray(size_type const _Newsize, T const& val) 
			: _m_start(), _m_last(), alloc(Alloc{})
		{
			__construct_n(_Newsize, val);
		}

		explicit valArray(size_type const _Newsize, Alloc al)
			: _m_start(), _m_last(), alloc(al)
		{
			__construct_n(_Newsize, T{});
		}

		valArray(const Alloc& al)
			: _m_start(), _m_last(), alloc(al) {}

		valArray(std::initializer_list<T> data) 
			: _m_start(), _m_last() 
		{
			__construct_range(data.begin(), data.end());
		}

		explicit valArray(std::initializer_list<T> data, Alloc al)
			: _m_start(), _m_last(), alloc(al)
		{
			__construct_range(data.begin(), data.end());
		}

		template<typename PointerType, 
			zuck::enable_if_t<zuck::is_iterator_v<PointerType>, int> = 0>
		explicit valArray(PointerType Begin, PointerType End) 
			: _m_start(), _m_last(), alloc(Alloc{})
		{
			__construct_range(Begin, End);
		}

		template<typename PointerType,
			zuck::enable_if_t<zuck::is_iterator_v<PointerType>, int> = 0>
		explicit valArray(PointerType Begin, PointerType End, Alloc al)
			: _m_start(), _m_last(), alloc(al)
		{
			__construct_range(Begin, End);
		}

        valArray(const valArray& other)
            : _m_start(), _m_last(), alloc(other.alloc)
        {
            __construct_range(other.begin(), other.end());
        }

        explicit valArray(valArray const& other, Alloc al)
            : _m_start(), _m_last(), alloc(al)
        {
            __construct_range(other.begin(), other.end());
        }

        valArray(valArray&& other)noexcept 
            : _m_start(other._m_start), _m_last(other._m_last) 
        {
            other._m_start = null;
            other._m_last  = null;
        }

		[[nodiscard]] inline bool empty()const noexcept {
			return static_cast<bool>(_m_start == _m_last);
		}

		inline void resize(size_type _Newsize) {
			__resize(_Newsize, T{});
		}

		inline void resize(size_type _Newsize, T const& val) {
			__resize(_Newsize, val);
		}

		inline void resize(size_type _Newsize, T&& val) {
			__resize(_Newsize, zuck::move(val));
		}

		[[nodiscard]] inline size_type size()const noexcept {
			return static_cast<size_type>(_m_last - _m_start);
		}

		[[nodiscard]] inline iterator begin()noexcept {
			return iterator(_m_start, _m_start, _m_last);
		}

		[[nodiscard]] inline const_iterator begin()const noexcept {
			return const_iterator(_m_start, _m_start, _m_last);
		}

		[[nodiscard]] inline const_iterator cbegin()const noexcept {
			return const_iterator(_m_start, _m_start, _m_last);
		}

		[[nodiscard]] inline iterator end()noexcept {
			return iterator(_m_last, _m_start, _m_last);
		}

		[[nodiscard]] inline const_iterator end()const noexcept {
			return const_iterator(_m_last, _m_start, _m_last);
		}

		[[nodiscard]] inline const_iterator cend()const noexcept {
			return const_iterator(_m_last, _m_start, _m_last);
		}

		inline valArray& operator=(valArray const& other) {
			__copy_construct(other);
			return *this;
		}

		inline valArray& operator=(valArray&& other)noexcept {
			__move_construct(zuck::move(other));
			return *this;
		}

		[[nodiscard]] inline reference operator[](size_type _Idx)noexcept
		{
			if constexpr (debug_) {
				if (zuck::out_of_range(_Idx, size())) {
					zuck::derror(zuck::SEG_OutofRange,
						"Segmentfault: subscript out of range !");
				}
			}
			return *(_m_start + _Idx);
		}

		[[nodiscard]] inline const_reference operator[](size_type _Idx)const noexcept
		{
			if constexpr (debug_) {
				if (zuck::out_of_range(_Idx, size())) {
					zuck::derror(zuck::SEG_OutofRange,
						"Segmentfault: subscript out of range !");
				}
			}
			return *(_m_start + _Idx);
		}

		valArray& operator+=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] += val;
			}

			return *this;
		}

		valArray& operator+=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0; 
				__i < _Size && __j < _Newsize; 
				++__i, ++__j) {
				_m_start[__i] += other[__j];
			}

			return *this;
		}

		valArray& operator-=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] -= val;
			}

			return *this;
		}

		valArray& operator-=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0;
				__i < _Size && __j < _Newsize; 
				++__i, ++__j) {
				_m_start[__i] -= other[__j];
			}

			return *this;
		}

		valArray& operator*=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] *= val;
			}

			return *this;
		}

		valArray& operator*=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0; 
				__i < _Size && __j < _Newsize; 
				++__i, ++__j) {
				_m_start[__i] *= other[__j];
			}

			return *this;
		}

		valArray& operator/=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] /= val;
			}

			return *this;
		}

		valArray& operator/=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0; 
				__i < _Size && __j < _Newsize; 
				++__i, ++__j) {
				_m_start[__i] /= other[__j];
			}

			return *this;
		}

		valArray& operator%=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] %= val;
			}

			return *this;
		}

		valArray& operator%=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0; 
				__i < _Size && __j < _Newsize; 
				++__i, ++__j) {
				_m_start[__i] %= other[__j];
			}

			return *this;
		}

		valArray& operator&=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] &= val;
			}

			return *this;
		}

		valArray& operator&=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0; 
				__i < _Size && __j < _Newsize; 
				++__i, ++__j) {
				_m_start[__i] &= other[__j];
			}

			return *this;
		}

		valArray& operator|=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] |= val;
			}

			return *this;
		}

		valArray& operator|=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0;
				__i < _Size && __j < _Newsize;
				++__i, ++__j) {
				_m_start[__i] |= other[__j];
			}

			return *this;
		}

		valArray& operator^=(const value_type& val)
		{
			auto const _Size = size();
			for (size_type __i = 0; __i < _Size; ++__i) {
				_m_start[__i] ^= val;
			}

			return *this;
		}

		valArray& operator^=(const _Self& other)
		{
			auto const _Size	= size();
			auto const _Newsize = other.size();
			for (size_type __i = 0, __j = 0;
				__i < _Size && __j < _Newsize;
				++__i, ++__j) {
				_m_start[__i] ^= other[__j];
			}

			return *this;
		}

	private:
		void __construct_n(size_type _Newsize, const T& val)
		{
			_m_start = alloc.allocate(_Newsize);
			for (_m_last = _m_start; 
				_Newsize; 
				++_m_last, --_Newsize) {
				zuck::construct(_m_last, val);
			}
		}

		template<typename PointerType>
		void __construct_range(PointerType Begin, PointerType End)
		{
			auto const _Newsize = zuck::distance(Begin, End);
            __verify_size(_Newsize);
            
            _m_start = alloc.allocate(_Newsize);
            _m_last  = _m_start;
            for (; Begin != End; ++Begin, ++_m_last) {
                zuck::construct(_m_last, *Begin);
            }
		}

		void __destroy_array()
		{
			zuck::destruct_range(_m_start, _m_last);
			alloc.deallocate(_m_start, size());
		}

		void __copy_construct(const _Self& other)
		{
			__construct_range(other.begin(), other.end());
		}

		void __move_construct(_Self&& other)
		{
			_m_start = other._m_start;
			_m_last  = other._m_last;
			other._m_start = null;
			other._m_last  = null;
		}

		void __resize(size_type _Newsize, const T& val)
		{
			__verify_size(_Newsize);

			alloc.deallocate(_m_start, size());
			_m_start = alloc.allocate(_Newsize);
			_m_last  = _m_start;
			for (; _Newsize; ++_m_last, --_Newsize) {
				zuck::construct(_m_last, val);
			}
		}

		void __verify_array()
		{
			if (!empty()) {
				__destroy_array();
			}
		}

		void __verify_size(size_type const _Size)
		{
			if (_Size > max_size()) {
				derror(zuck::SEG_LengthTooLong, 
					"val array's length too long !");
			}
		}
	};


    template<typename T, typename Alloc = zuck::allocator<T>>
    valArray(zuck::size_t, T const&)->valArray<T, Alloc>;

	template<typename T, typename Alloc = zuck::allocator<T>>
	valArray(zuck::size_t, Alloc)->valArray<T, Alloc>;

	template<typename T, typename Alloc = zuck::allocator<T>>
	valArray(std::initializer_list<T>)->valArray<T, Alloc>;

	template<typename T, typename Alloc = zuck::allocator<T>>
	valArray(std::initializer_list<T>, Alloc)->valArray<T, Alloc>;

	template<typename PointerType, typename T, typename Alloc = zuck::allocator<T>>
	valArray(PointerType, PointerType)->valArray<T, Alloc>;

	template<typename PointerType, typename T, typename Alloc = zuck::allocator<T>>
	valArray(PointerType, PointerType, Alloc)->valArray<T, Alloc>;

	template<typename T, typename Alloc = zuck::allocator<T>>
	valArray(valArray<T, Alloc> const&)->valArray<T, Alloc>;

	template<typename T, typename Alloc = zuck::allocator<T>>
	valArray(valArray<T, Alloc>&&)->valArray<T, Alloc>;
}

